<?php
/**
 * A simple class to manipulate vCard data.
 * (Borrowed from Leeds Met Repository Open Search)
 * @author Mike Taylor (m.j.taylor@leedsmet.ac.uk)
 *
 */
class VCard
{
	/**
	 * Key => Value pairs of vCard data
	 * @var array
	 */
	protected $data_;
	
	/**
	 * Constructor for VCard
	 * 
	 * @return void
	 */
	public function __construct()
	{
		$this->data_ = array();
	}
	
	/**
	 * Adds a new field to the vCard data with the specified fieldname and value 
	 * 
	 * @param $fieldName
	 * @param $value
	 * @return bool
	 */
	public function addField($fieldName, $value)
	{
		$hasKey = array_key_exists($fieldName, $this->data_);
		
		if (!$hasKey)
		{
			$this->data_[$fieldName] = $value;
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Updates the specified field in the vCard data with a new value
	 * 
	 * @param $fieldName
	 * @param $value
	 * @return bool
	 */
	public function updateField($fieldName, $value)
	{
		$hasKey = array_key_exists($fieldName, $this->data_);
		
		if ($hasKey)
		{
			$this->data_[$fieldName] = $value;
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Removes the specified field from the vCard data
	 * 
	 * @param $fieldName
	 * @return unknown_type
	 */
	public function removeField($fieldName)
	{
		unset($this->data_[$fieldName]);
	}
	
	/**
	 * Clears all data from the VCard
	 * @return void
	 */
	public function clearAllFields()
	{
		$this->data_ = array();
	}
	
	/**
	 * Gets the value of the specified field from the vCard
	 * 
	 * @param $fieldName
	 * @return mixed
	 */
	public function getFieldValue($fieldName)
	{
		$hasKey = array_key_exists($fieldName, $this->data_);
		
		if ($hasKey)
		{
			return $this->data_[$fieldName];
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * Adds/updates the value of the specified field in the vCard. Will
	 * overwrite existing data by default
	 * 
	 * @param $fieldName
	 * @param $value
	 * @param $shouldOverwrite
	 * @return void
	 */
	public function setFieldValue($fieldName, $value, $shouldOverwrite = true)
	{
		$hasKey = array_key_exists($fieldName, $this->data_);
		
		if ($hasKey && $shouldOverwrite)
		{
			$this->updateField($fieldName, $value);
		}
		else
		{
			$this->addField($fieldName, $value);
		}
	}
	
	/**
	 * Loads vCard data from a string representation using the specified mode to
	 * control how existing data is treated.
	 * 
	 * The modes are as follows:
	 * 	0 - Overwrite current data
	 * 	1 - Merge, adding only new fields
	 * 	2 - Update existing data only, ignore new fields
	 * 
	 * @param $string
	 * @param $mode
	 * @return bool
	 */
	public function loadFromString($string, $mode = 0)
	{
		// Need to have the new line chars consistent throughout the string for
		// processing.
		// 18-05-2010 - Modified the regex to also remove SPACEs following new
		// line chars so that field codes are always valid.
		//$string = preg_replace('/[\r\n]+/', "\n", $string); // Previous regex
		$string = trim($string);
		$string = preg_replace('/[\r\n]+ ?/', "\n", $string);
		$lines = split("\n", $string);
		$lineCount = count($lines);
		
		$hasBeginLine = strcasecmp($lines[0], 'BEGIN:vcard') == 0;
		$hasEndLine = strcasecmp($lines[$lineCount - 1], 'END:vcard') == 0;
		
		if ($hasBeginLine && $hasEndLine)
		{
			$dataLines = array_slice($lines, 1, -1);
			
			switch ($mode)
			{
				// Overwrite current data
				case 0 :
					$this->clearAllFields();

					foreach ($dataLines as $line)
					{
						$fieldAndValue = $this->splitFieldAndValueString($line);
						
						if ($fieldAndValue !== false)
						{
							$this->addField($fieldAndValue[0], $fieldAndValue[1]);
						}
					}
					
					break;
					
				// Merge, adding only new fields
				case 1 :
					foreach ($dataLines as $line)
					{
						$fieldAndValue = $this->splitFieldAndValueString($line);
						
						if ($fieldAndValue !== false)
						{
							// addField() checks for existing value so won't overwrite
							$this->addField($fieldAndValue[0], $fieldAndValue[1]);
						}
					}
					
					break;
					
				// Update existing data only, ignore new fields
				case 2 :
					foreach ($dataLines as $line)
					{
						$fieldAndValue = $this->splitFieldAndValueString($line);
						
						if ($fieldAndValue !== false)
						{
							$hasKey = array_key_exists($fieldAndValue[0], $this->data_);
							if ($hasKey)
							{
								$this->updateField($fieldAndValue[0], $fieldAndValue[1]);
							}
						}
					}
					
					break;
					
				default :
					// No action
			}
		}
		else
		{
			// vCard string doesn not begin/end correctly
			return false;
		}

		return true;
	}
	
	/**
	 * Splits a line of data from a vCard string representation into the
	 * fieldname and value parts.
	 * 
	 * @param $string
	 * @return mixed
	 */
	protected function splitFieldAndValueString($string)
	{
		// COLON that is not preceded by a backslash (i.e. unescaped)
		$splitRegExp = '/(?<!\\\\):/';
		
		$parts = preg_split($splitRegExp, $string, 2);

		if (is_array($parts) && count($parts) == 2)
		{
			return $parts;
		}
		else
		{
			return false;
		}
	}
}
?>